/*
The surface-physics project: http://code.google.com/p/surface-physics/
Adds real world physics based on the Bullet physics engine, to bases in the Orbiter space
flight simulation software.

Written by Abhijit Nandy
License : Apache License 2.0

Bump_BulletBase.cpp
BulletBase management implementation.

This file implements the Bump_BulletBase class.

*/


/* Class declaration */
#include "Bump_BulletBase.h"
#include "../Engine/PhysicsEngine.h"
#include "../BulletBase/Bump_BaseObject.h"
#include "../Terrain/Bump_TerrainPatch.h"


/**
 * NOTE: Bump_ATV is a friend of Bump_BulletBase
 * Must include its header in .cpp ONLY
 * Including in header will cause compile errors
 */
/*

//Attachment includes
#include "../AttachmentLibrary/Bump_ATV.h"

//Component includes
#include "../ComponentLibrary/Bump_ExternalStaticGeometry.h"
#include "../ComponentLibrary/Bump_Ball.h"
#include "../ComponentLibrary/Bump_Plate.h"
#include "../ComponentLibrary/Bump_CraterRamp.h"
*/
#include <string>


extern PhysicsEngine bump;

struct packet pkt;
DWORD baseCmd;

extern bool ORUCheck();
extern double ORUGetMaxElev();

unsigned long Bump_BulletBase::simStep = 0;
double Bump_BulletBase::SimT = 0. ;
double Bump_BulletBase::SimDT = 0.;
double Bump_BulletBase::MJD = 0.;

BaseFactory objectFactory;

/**
 * Constructor for the class
 * Will be used later, when objects created dynamically.
 * Do NOT allocate memory here, let init() do that
 */
Bump_BulletBase::Bump_BulletBase():
		MAX_TERRAIN_ELEVATION(8000.f), //10 km
		count(0)
{
	mode = IDLE;	
	physicsWorldOffset.setValue(0.f, 0.f, 0.f);	

	cutoff = 2.0;
}


/**
 * Destructor, does nothing currently
 */
Bump_BulletBase::~Bump_BulletBase()
{}


/**
 * Initialize a base from its OBJHANDLE.
 * Creates a new BulletBase and inserts it in the Orbiter world
 *
 */
int
Bump_BulletBase::init(OBJHANDLE hObj, OBJHANDLE hObjRBody)
{
	
	VESSELSTATUS2 vsBB;
	VECTOR3 gposMoon, gposBase;


	oapiWriteLog("Bump_BulletBase::init: Initializing...");

	//This will change midway through the sim ?
	hObjRefBody = hObjRBody;
	hObjBase = hObj;
	oapiGetObjectName(hObjBase, strBaseName, 20);


	//Create a BulletBase VESSEL at the origin of Brighton Beach,
	//get equatorial location(latitude, long location on 2d moon surface)


	//oapiGetBasePadEquPos(hObjBase, 0, &lng, &lat, &rad);//To create base at pad 1 for DEBUG

	oapiGetBaseEquPos(hObjBase, &lng, &lat, &rad);

	//Convert to global ecliptic co-ord.
	oapiEquToGlobal(hObjBase, lng, lat, rad, &gposBase);

	//Get reference body(nearest celestial body) co-ordinates for BB rpos calc.
	oapiGetGlobalPos(hObjRefBody, &gposMoon);
	radRefBody = oapiGetSize(hObjRefBody);


	//Zero out the VESSELSTATUS2 structure to prevent unwanted vessel states
	memset(&vsBB,0,sizeof(VESSELSTATUS2));
	vsBB.version = 2;
	vsBB.status = 1; //landed
	vsBB.rbody = hObjRefBody;
	vsBB.rpos = gposBase - gposMoon;
	vsBB.surf_lng = lng;
	vsBB.surf_lat = lat;
	vsBB.surf_hdg = PI/2;


	//Create the BulletBase in a landed state with same position as the passed Base
	hObjBulletBase = oapiCreateVesselEx(strBaseName, "BulletBase", &vsBB);

	//Cast and keep proper pointers to the associated BulletBase
	vBulletBase = (VESSEL3*)(oapiGetVesselInterface(hObjBulletBase));
	bb = (BulletBase*)(vBulletBase);
	vBulletBase->SetSize(BULLET_WORLD_SIZE);
	
	mass = bb->GetMass();
	bb->GetWeightVector(G);  //Get gravity force using

	G /= mass;


	//Terrain creation handled in poststep(),best not to do too much be4 sim gets underway

	mode = CREATED_BB;

	//TODO: Read physics file for properties like cutoff ?
	//readPhysicsFile();

	sprintf(output, "Bump_BulletBase::init : Created BulletBase %s", strBaseName);
	oapiWriteLog(output);

	return 0;

}


/**
 * Updates the following:
 * 1. Opens the scenario specific basename.txt physics file from the Physics folder
 * 2. Attaches or creates the listed objects in the file over a number of sim steps
 * 3. Continues to update attachment points with transforms or send them to BB if needed
 * 4. Processes commands from all objects managed by this BB and BB itself by reading member
 *
 * TODO: Implement frequency of updates list and do not update inactive objects or static geometry objects
 */
int
Bump_BulletBase::prestep(double SimT, double SimDT, double MJD)
{
	return 0;
}


int
Bump_BulletBase::poststep(double SimT, double SimDT, double MJD)
{
	return 0;
}
/**
 * Updates the following:
 * 1. Opens the scenario specific basename.txt physics file from the Physics folder
 * 2. Attaches or creates the listed objects in the file over a number of sim steps
 * 3. Continues to update attachment points with transforms or send them to BB if needed
 * 4. Processes commands from all objects managed by this BB and BB itself by reading member
 *
 * TODO: Implement frequency of updates list and do not update inactive objects or static geometry objects
 */
int
Bump_BulletBase::run()
{
	++count;
	oapiWriteLogV("Bump_BulletBase::run: %s : %d (%f, %f, %f)", strBaseName, count, SimT, SimDT, MJD);



	return 0;

}


/**
 * Free all objects first, de-allocating their memory
 * Each object will clears it handles etc and any handles it made with it's current BulletBase
 * (if a VESSEL was attached/detached midway in the sim, it should have properly cleared the base handle)
 *
 * Sets all values in base object back to default in case this object is reused
 */
int
Bump_BulletBase::exit(void)
{
	int i, rv;

	sprintf(output, "Bump_BulletBase::exit: CALLED : %s ", strBaseName);
	oapiWriteLog(output);

	//Cleanup all objects, if exit() fails on a sub object, return error, but still delete it's ptr
	for(objIter = baseObjList.begin(), i=1;  objIter != baseObjList.end(); objIter++, i++){

		sprintf(output, "Bump_BulletBase::exit: calling exit() for object :%d : %s ",
				i, (*objIter)->strObjectName);
		oapiWriteLog(output);
	
		//iterator is ptr to ptr to struct BaseObject, (*objIter) gives the struct BaseObject*
		rv = (*objIter)->exit(); 
		if(rv){
			sprintf(output, "Bump_BulletBase::exit: ERROR : exit() failed on sub class object %s " \
								"create physics counterpart %s", (*objIter)->strObjectName);
			oapiWriteLog(output);
		}

		delete (*objIter);
	}
	
	baseObjList.clear();

	//Base cleanup
	mode = IDLE;	
	physicsWorldOffset.setValue(0.f, 0.f, 0.f);

	return 0;
}

/**
 * Utility function to read the description from a physics file
 */
void
Bump_BulletBase::readPhysicsFileDescription(void)
{
	char *line;

	while(physicsFile && oapiReadScenario_nextline (physicsFile, line)) {
		sprintf(output, "Bump_BulletBase::readPhysicsFileDescription: %s", line);
		oapiWriteLog(output);

		if(strcmp(line, "END_DESC") == 0){
			break;
		}
	}

}


/**
 * Utility function to read 1 object from a physics file and create it
 * Will return immediately if END or END_OBJECTS found
 * Skips blank lines or lines not containing ':'
 *
 * TODO: Move object creation to separate function
 */
void
Bump_BulletBase::readCreatePhysicsFileObject(void)
{
	char *line, *sep;
	char strObjName[MAX_STRLEN], strType[MAX_STRLEN];
	
	//Read the next line and create the object
	while(physicsFile && oapiReadScenario_nextline (physicsFile, line)) {
		sprintf(output, "Bump_BulletBase::readCreatePhysicsFileObject: %s", line);
		oapiWriteLog(output);

		//Skip blank lines
		if(strlen(line) == 0 || line[0] == ';')
			continue;

		//Don't parse section labels
		if((strcmp(line, "END") == 0) ||
		   (strcmp(line, "END_OBJECTS") == 0)
		   ){
			break;
		}

		//Should now be an object descriptor line, check for ':', else skip
		if((sep = strchr(line, ':'))){

			//Get the name and type as strings
			strncpy(strObjName, line, sep - line);
			strObjName[sep - line] = '\0';
			strcpy(strType, sep + 1);


			sprintf(output, "Bump_BulletBase::readCreatePhysicsFileObject: Object Name:%s, Type:%s",
					strObjName, strType);
			oapiWriteLog(output);

			//createBaseObject(strType, strObjName, line);

			createTypedBaseObject(strType, strObjName);

		}


	}

}


btRigidBody*
Bump_BulletBase::localCreateRigidBody(float mass, const btTransform& startTransform,btCollisionShape* shape)
{
	btAssert((!shape || shape->getShapeType() != INVALID_SHAPE_PROXYTYPE));

	//rigidbody is dynamic if and only if mass is non zero, otherwise static
	bool isDynamic = (mass != 0.f);

	btVector3 localInertia(0,0,0);
	if (isDynamic)
		shape->calculateLocalInertia(mass,localInertia);

	//using motionstate is recommended, it provides interpolation capabilities, and only synchronizes 'active' objects

	btRigidBody* body = new btRigidBody(mass,0,shape,localInertia);
	body->setWorldTransform(startTransform);
	body->setContactProcessingThreshold(BT_LARGE_FLOAT);
	m_dynamicsWorld->addRigidBody(body);

	return body;
}


/**
 * create object using type name and switch-case
 */
int
Bump_BulletBase::createBaseObject(char *strType, char *strObjName, char *line)
{
/*	int rv;

	//Now create the proper subtype of Bump_BaseObject
	if(strcmp(strType,"ATV")==0 && numObjects>=0 && numObjects<MAX_OBJECTS){		
		bbObj[numObjects] = new Bump_ATV();		
	}
	else if(strcmp(strType,"ExternalStaticGeometry")==0 && numObjects>=0 && numObjects<MAX_OBJECTS){		
		bbObj[numObjects] = new Bump_ExternalStaticGeometry();		
	}
	else if(strcmp(strType,"Ball")==0 && numObjects>=0 && numObjects<MAX_OBJECTS){		
		bbObj[numObjects] = new Bump_Ball();		
	}
	else if(strcmp(strType,"Plate")==0 && numObjects>=0 && numObjects<MAX_OBJECTS){		
		bbObj[numObjects] = new Bump_Plate();		
	}
	else if(strcmp(strType,"CraterRamp")==0 && numObjects>=0 && numObjects<MAX_OBJECTS){		
		bbObj[numObjects] = new Bump_CraterRamp();		
	}
	else{
		sprintf(output, "Bump_BulletBase::createBaseObject: %s", line);

		if(numObjects == MAX_OBJECTS)
			sprintf(output, "%s, ERROR Number of objects:%d = MAX_OBJECTS %d",
				output, numObjects, MAX_OBJECTS);

		oapiWriteLog(output);

		return 1;

	}

	//Check the objects
	if(!bbObj[numObjects]){
		bbObj[numObjects] = NULL;

		sprintf(output, "Bump_BulletBase::createBaseObject: ERROR : "\
				"objectFactory failed to create object of subclass %s for %s",
				strType, strObjName);
		oapiWriteLog(output);

		return 2;

	}

	rv = bbObj[numObjects]->init(this, strObjName);

	//Increment only if object created, else de-allocation from bbObj[] will cause crash
	if(!rv){
		numObjects++;
	}
	else{
		//De-allocate!, something went wrong, do not allocate memory in constructor
		delete bbObj[numObjects];
		bbObj[numObjects] = NULL;

		sprintf(output, "Bump_BulletBase::createBaseObject: ERROR : "\
				"init() failed on subclass %s object: %s with rv=%d, Deleted object",
				strType, strObjName, rv);
		oapiWriteLog(output);

		return 3;
	}
*/	
	return 0;


}


/**
 * Create sub class object based on a table and template
 */
inline int
Bump_BulletBase::createTypedBaseObject(char *strType, char *strObjName, OBJHANDLE hObj) {
	int rv;

	//Now create the proper subtype of Bump_BaseObject
	if(strType && strObjName){

		std::string s(strType);
		s = "Bump_" + s;

		//Set the base right away
		Bump_BaseObject *objptr = objectFactory.createInstance(s);

		if(!objptr){

			sprintf(output, "Bump_BulletBase::createTypedBaseObject: ERROR : "\
					"objectFactory failed to create object of subclass %s for %s",
					strType, strObjName);
			oapiWriteLog(output);

			return 1;

		}
		else{
			objptr->freq  = 0;
			baseObjList.push_back(objptr);
		}


		
		//Find or create strObjName and attach to base, if rv!=0, deallocate imm.
		//Separate function init() used to find/create object as it can return an error value
		//Constructor cant.
		//If handle of object available, pass it, some classes implement without init() without hObj
		//These will search for the vessel each time, in case they control one
		if(hObj)
			rv = objptr->init(this, strObjName, hObj);
		else
			rv = objptr->init(this, strObjName);  //hObj is NULL

		//Increment only if object created, else de-allocation from bbObj[] will cause crash
		if(rv){
			//De-allocate!, something went wrong, do not allocate memory in constructor
			delete objptr;
			baseObjList.pop_back();			

			sprintf(output, "Bump_BulletBase::createTypedBaseObject: ERROR : "\
					"init() failed on subclass %s object: %s with rv=%d, Deleted object",
					strType, strObjName, rv);
			oapiWriteLog(output);

			return 2;
		}

		return 0;
	}

	sprintf(output, "Bump_BulletBase::createTypedBaseObject: ERROR : "\
						"Did not try making object of subclass %s object for %s, numObject=%d",
						strType, strObjName, baseObjList.size());
	oapiWriteLog(output);


	return 3;
}


/**
 * Get distances of objects from base
 * Skip attached vessels, wrapped vessels, landed vessels, vehicles with different ref body than base
 * Dont use sqrt(), compare squares directly with square of min distance.
 */
void
Bump_BulletBase::rangingPass(bool skipLanded)
{

	unsigned int i;
	OBJHANDLE hObj;
	char strVesselName[MAX_STRLEN];
	VESSEL3 *v;


	//Search the Orbiter sim for this vessel
	for (i = 0; i<oapiGetVesselCount(); i++) {

		//Assuming hObj will be non-NULL
		hObj = oapiGetVesselByIndex(i);
		oapiGetObjectName(hObj, strVesselName, MAX_STRLEN);
		v = (VESSEL3*)oapiGetVesselInterface(hObj);

	/*	sprintf(output, "Bump_BulletBase::rangingPass(%d): %3d. Found : %s", skipLanded, i+1, strVesselName);
		oapiWriteLog(output);
*/


		//Skips vessels with different gravity reference than this base
		OBJHANDLE hObjGravityRef = v->GetGravityRef();
		if(hObjGravityRef != hObjRefBody){
		/*	sprintf(output, "Bump_BulletBase::rangingPass: Diff reference for %s, %d", strVesselName, d);
			oapiWriteLog(output);*/
			continue;
		}

		//Skip ships already under base control or landed if requested(not requested during scn start)
		DWORD d = v->GetFlightStatus();
		if(skipLanded && (d == 1 || d == 3)){		//|| d == 4 || d == 7 || d == 6 || d == 5){
	/*		sprintf(output, "Bump_BulletBase::rangingPass: Skipping %s, %d", strVesselName, d);
			oapiWriteLog(output);*/
			continue;
		}
		
		//All clear to get altitude, bodies left are above moon & not under control & in flight
		if(terrainGenerationEnabled && v->GetAltitude() <= MAX_TERRAIN_ELEVATION){
			//Create a wrapper for this vessel
			//TODO: May need to modify this after BulletVessel class is finished


			//Is object already under control ? Will be present in objMap if so.
			objMapIter = objMap.find(hObj);
			if(objMapIter != objMap.end()){
		/*		sprintf(output, "Bump_BulletBase::rangingPass: Found in MAP %s, %d", strVesselName, d);
			   oapiWriteLog(output);*/
			   continue;
			}

			//Check if already attached to parent or child
			if(checkAttachments(v)){
	/*			sprintf(output, "Bump_BulletBase::rangingPass: Vessel %s is attached already", strVesselName);
				oapiWriteLog(output);*/
				continue;
			}


			//All pre-conditions met, create the wrapper
			createTypedBaseObject("VesselWrapper", strVesselName, hObj);  //DO NOT ADD Bump_


		/*	sprintf(output, "Bump_BulletBase::rangingPass: Adding %s(%p,%p) to Control Map",
					strVesselName, hObj, baseObjList.back());
			oapiWriteLog(output);*/

			objMap.insert(BaseObjectMap::value_type(hObj, baseObjList.back() ));

			//printControlMap();

		}

	}

}


/**
 * Check if attached to anything currently, return true if so, else false
 */
bool
Bump_BulletBase::checkAttachments(VESSEL3 *v)
{
	//Check attachments to parents
	DWORD count = v->AttachmentCount(true);
	unsigned int i;

	for(i=0; i<count; i++)
		if( v->GetAttachmentStatus(v->GetAttachmentHandle(true, i)))
			return 1;

	//Check attachments to children
	count = v->AttachmentCount(false);

	for(i=0; i<count; i++)
		if( v->GetAttachmentStatus(v->GetAttachmentHandle(false, i)))
			return 1;

	return 0;
}

/**
 * Called to refresh list of active sections, priority is TO DELETE sections as much as possible
 * to keep number of active sections absolutely minimal.
 *
 * All deletions/loads based on camera position and adjacency graph.
 *
 * Graph can mark certain sections as never to be removed(such as external sections)
 *
 * 1. Loads sections from file based on camera position : should be threaded off
 * 2. Unloads them if their deleteCountdown has reached 0
 * 3. Updates the deleteCountdown of active sections
 *
 *
 *
 */
void
Bump_BulletBase::refreshSections(void)
{

	//Convert camera position in base relative co-ordinates

	VECTOR3 rposCamera, gposCamera;

	//TODO: Move this out of here, multiple bases should make just 1 call to camera position
	oapiCameraGlobalPos(&gposCamera);


	//The bases can convert the camera position to their own relative co-ords as needed
	bb->Global2Local(gposCamera, rposCamera);



	//sprintf(oapiDebugString(), "Camera at : %f, %, %f", rposCamera.x,  rposCamera.y,  rposCamera.z);

	//Every sections has its cutoff(section unloaded after camera further than this point or most stuff unloaded
	//:idling)




}


/**
 *	Delete object midway insim due to request from another object(so this function
 *	is not used for self request with deleteMe). The external object must at least
 *	specify the Orbiter OBJHANDLE.
 *
 *	The object is setup to be deletedremoved from the control map as well as object list.
 *
 *	Generally used for VESSELs in Orbiter which DO have a OBJHANDLE
 *	Lookup object using Orbiter handle & delete resulting BaseObject* if found
 */
int
Bump_BulletBase::dropBaseObject(OBJHANDLE hObjDrop)
{
/*	sprintf(output, "Bump_BulletBase::dropBaseObject: Called to DROP %p", hObjDrop);
	oapiWriteLog(output);*/

	//printControlMap();

	
	objMapIter = objMap.find(hObjDrop);
	if(objMapIter != objMap.end()){
	/*   sprintf(output, "Bump_BulletBase::dropBaseObject: Found in MAP, setting up SELF-DELETION...");
	   oapiWriteLog(output);*/

	   (objMapIter->second)->deleteMe = true;

	   return 0; //all izz well
	}	


	return 1; //Object not found
}


/**
 *	Prints the control map, useful to debug
 */
int
Bump_BulletBase::printControlMap(void)
{
	sprintf(output, "Bump_BulletBase::printControlMap: %d objects in map", objMap.size());
	oapiWriteLog(output);
	
	sprintf(output, "");
	for(objMapIter = objMap.begin();  objMapIter != objMap.end(); objMapIter++)
	   sprintf(output, "%s(%p, %p)\n", output, (objMapIter->first),  (objMapIter->second) );
	

	oapiWriteLog(output);

	return 0;
}


/**
 *	Prints the object list, useful to debug
 */
int
Bump_BulletBase::printObjectList(void)
{
	sprintf(output, "Bump_BulletBase::printObjectList: %d objects in list", baseObjList.size());
	oapiWriteLog(output);
	
	sprintf(output, "");
	for(objIter = baseObjList.begin();  objIter != baseObjList.end(); objIter++)
		sprintf(output, "Found: %s\n", output, (*objIter)->strObjectName);
	

	oapiWriteLog(output);

	return 0;
}
